#include "defs.h"
#include <netinet/in_var.h>
#include "snmp.h"
#include "snmplib/asn1.h"
#include "snmplib/party.h"
#include "snmplib/snmp_impl.h"
#define MROUTED
#include "snmpd/snmp_vars.h"
#include "snmplib/snmp.h"
#include "snmpd/m2m.h" /* for RowStatus values */
#include <sys/stat.h>  /* for stat() */

    u_short dest_port = 0;
    int sdlen = 0;

struct addrCache {
    u_long addr;
    int status;
#define UNUSED 0
#define USED   1
#define OLD 2
};

static struct addrCache addrCache[10];

struct ifreq *   ifconfaddr __P((struct ifconf *ifcp, u_int32 a));
void save_data();

/*
 * Initialize the SNMP part of mrouted
 */
int /* returns: 0 on success, true on error */
snmp_init()
{
   u_long myaddr;
   int ret;
   struct partyEntry *pp;
   struct sockaddr_in  me;
   int index, sd, portlist[32];

   init_snmp();
   /* init_mib(); why was this here? */
    if (read_party_database("/etc/party.conf") > 0){
   fprintf(stderr, "Couldn't read party database from /etc/party.conf\n");
   exit(0);
    }
    if (read_context_database("/etc/context.conf") > 0){
   fprintf(stderr, "Couldn't read context database from /etc/context.conf\n");
   exit(0);
    }
    if (read_acl_database("/etc/acl.conf") > 0){
   fprintf(stderr, "Couldn't read acl database from /etc/acl.conf\n");
   exit(0);
    }
    if (read_view_database("/etc/view.conf") > 0){
   fprintf(stderr, "Couldn't read view database from /etc/view.conf\n");
   exit(0);
    }

    myaddr = get_myaddr();
    if (ret = agent_party_init(myaddr, ".1.3.6.1")){
   if (ret == 1){
       fprintf(stderr, "Conflict found with initial noAuth/noPriv parties... continuing\n");
   } else if (ret == -1){
       fprintf(stderr, "Error installing initial noAuth/noPriv parties, exiting\n");
       exit(1);
   } else {
       fprintf(stderr, "Unknown error, exiting\n");
       exit(2);
   }
    }

    printf("Opening port(s): ");
    fflush(stdout);
    party_scanInit();
    for(pp = party_scanNext(); pp; pp = party_scanNext()){
   if ((pp->partyTDomain != DOMAINSNMPUDP)
       || bcmp((char *)&myaddr, pp->partyTAddress, 4))
       continue;  /* don't listen for non-local parties */

   dest_port = 0;
   bcopy(pp->partyTAddress + 4, &dest_port, 2);
   for(index = 0; index < sdlen; index++)
       if (dest_port == portlist[index])
      break;
   if (index < sdlen)  /* found a hit before the end of the list */
       continue;
   printf("%u ", dest_port);
   fflush(stdout);
   /* Set up connections */
   sd = socket(AF_INET, SOCK_DGRAM, 0);
   if (sd < 0){
       perror("socket");
       return 1;
   }
   me.sin_family = AF_INET;
   me.sin_addr.s_addr = INADDR_ANY;

   /* already in network byte order (I think) */
   me.sin_port = dest_port;
   if (bind(sd, (struct sockaddr *)&me, sizeof(me)) != 0){
#ifdef ALTERNATE_PORT
       perror("bind");
       me.sin_port = dest_port = 9161;
       printf("%u ", dest_port);
       fflush(stdout);
       if (bind(sd, (struct sockaddr *)&me, sizeof(me)) != 0){
          perror("bind");
          return 2;
       }
#else
       perror("bind");
       return 2;
#endif
   }

   register_input_handler(sd, snmp_read_packet);
   portlist[sdlen] = dest_port;
   if (++sdlen == 32){
       printf("No more sockets... ignoring rest of file\n");
       break;
   }
    }
    printf("\n");
    bzero((char *)addrCache, sizeof(addrCache));
}

/*
 * Place an IP address into an OID starting at element n
 */
void
put_address(name, addr, n)
   oid	 *name;
   u_long addr;
   int n;
{
   int i;

   for (i=n+3; i>=n+0; i--) {
      name[i] = addr & 0xFF;
      addr >>= 8;
   }
}

/* Get an IP address from an OID starting at element n */
int
get_address(name, length, addr, n)
   oid	 *name;	
   int	  length;
   u_long *addr;
   int n;
{
   int i;
   int ok = 1;

   (*addr) = 0;

   if (length < n+4)
      return 0;

   for (i=n; i<n+4; i++) {
      (*addr) <<= 8;
      if (i >= length)
          ok = 0;
      else
         (*addr) |= name[i];
   }
   return ok;
}

/*
 * Implements scalar objects from DVMRP and Multicast MIBs
 */
u_char *
o_scalar(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that points here */
    register oid	*name;	    /* IN/OUT - input name requested, output name found */
    register int	*length;    /* IN/OUT - length of input and output oid's */
    int			exact;	    /* IN - TRUE if an exact match was requested. */
    int			*var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set variable, otherwise 0 */
{
    int result;

    *write_method = 0;
    result = compare(name, *length, vp->name, (int)vp->namelen);
    if ((exact && (result != 0)) || (!exact && (result >= 0)))
   return NULL;

	bcopy((char *)vp->name, (char *)name,
     (int)vp->namelen * sizeof(oid));
	*length = vp->namelen;
	*var_len = sizeof(long);

    switch (vp->magic) {

    case ipMRouteEnable:
       long_return = 1;
       return (u_char *) &long_return;

    case dvmrpVersion: {
       static char buff[15];

       sprintf(buff, "mrouted%d.%d", PROTOCOL_VERSION, MROUTED_VERSION);
       *var_len = strlen(buff);
       return (u_char *)buff;
    }

    case dvmrpGenerationId:
       long_return = dvmrp_genid;
       return (u_char *) &long_return;

    case dvmrpNumRoutes:
       long_return = nroutes;
       return (u_char *) &long_return;

    case dvmrpReachableRoutes: {
       extern int routes_reach;
       long_return = routes_reach;
       return (u_char *) &long_return;
    }

#ifdef EXTRA_SNMP
    case dvmrpGroups: {
       extern int ngroups;
       long_return = ngroups;
       return (u_char *) &long_return;
    }

    case dvmrpCacheEntries:
       long_return = kroutes;
       return (u_char *) &long_return;

    case dvmrpActiveCacheEntries: {
       extern int aroutes;
       long_return = aroutes;
       return (u_char *) &long_return;
    }
#endif /* EXTRA_SNMP */

    default:
       ERROR("");
    }
    return NULL;
}

/*
 * checks for scoped multicast addresses
 * different from prune.c's version since addr can be any, not just 239.x.x.x
 */
#define GET_SCOPE(gt) { \
   register int _i; \
       for (_i = 0; _i < numvifs; _i++) \
      if (scoped_addr(_i, (gt)->gt_mcastgrp)) \
          VIFM_SET(_i, (gt)->gt_scope); \
   }
/*
 * dither cache lifetime to obtain a value between x and 2*x
 */
#ifdef SYSV
#define CACHE_LIFETIME(x) ((x) + (lrand48() % (x)))
#else
#define CACHE_LIFETIME(x) ((x) + (random() % (x)))
#endif

/* 
 * Add a scoped boundary to a vif 
 */
struct vif_acl * 
create_boundary(vifi, addr, mask) 
   vifi_t vifi;  /* IN: vif index */
   u_long addr;  /* IN: multicast address */
   u_long mask;  /* IN: multicast address mask */
{
extern struct gtable *kernel_table;     /* ptr to list of kernel grp entries*/
extern int cache_lifetime;
   struct vif_acl *v_acl;
   struct gtable *gt;

   v_acl = (struct vif_acl *)malloc(sizeof(struct vif_acl));
   if (v_acl == NULL)
      log(LOG_ERR, 0, "ran out of memory");    /* fatal */
   v_acl->acl_mask = mask;
   v_acl->acl_addr = addr & v_acl->acl_mask;
   if (addr & ~v_acl->acl_mask)
      log(LOG_WARNING,0,"Boundary spec %s/%d has host bits set", 
       inet_fmt(addr,s1),mask);
   v_acl->acl_next    = uvifs[vifi].uv_acl;
   uvifs[vifi].uv_acl = v_acl;

   /* update current group entries */
   for (gt = kernel_table; gt; gt = gt->gt_gnext) {
      if ((gt->gt_mcastgrp & mask) == addr) {
          if (vifi==gt->gt_route->rt_parent)
             gt->gt_scope = -1;
          else
             VIFM_SET(vifi, gt->gt_scope);
            
          /* Update kernel state */
          gt->gt_grpmems &= ~gt->gt_scope;
          prun_add_ttls(gt);
          update_kernel(gt);

          /* Check if we want to prune this group */
          if (!gt->gt_prsent_timer && gt->gt_grpmems == 0 
           && gt->gt_route->rt_gateway) {
             gt->gt_timer = CACHE_LIFETIME(cache_lifetime);
             send_prune(gt);
          }

      }
   }
}

/* 
 * Remove a scoped boundary from a vif 
 */
void
destroy_boundary(vifi,acl)
   vifi_t vifi;
   struct vif_acl *acl;
{
extern struct gtable *kernel_table;     /* ptr to list of kernel grp entries*/
   struct vif_acl *n, *p=NULL;
   struct gtable *gt;

   for (n=uvifs[vifi].uv_acl; n && n!=acl; p=n,n=n->acl_next);
   if (!n) /* should never happen */
      return;
   
   if (p)
      p->acl_next        = n->acl_next;
   else
      uvifs[vifi].uv_acl = n->acl_next;

   /* update current group entries */
   for (gt = kernel_table; gt; gt = gt->gt_gnext) {
      if ((ntohl(gt->gt_mcastgrp) & 0xff000000) == 0xef000000) 
         if ((gt->gt_mcastgrp & acl->acl_mask) == acl->acl_addr) {

             /* Recalculate scope */
             gt->gt_scope = 0;
             GET_SCOPE(gt);

             /* We dont need to tell the kernel.  Data will start flowing
              * by itself when a graft is received or the prune times
              * out. 
              */
             prun_add_ttls(gt);
             update_kernel(gt);
         }
   }

   free((char *)acl);
}

/*
 * Find if a specific alternate subnet exists on a Vif
 */
struct phaddr *
find_altnet(vifi, addr, mask)
   vifi_t vifi;
   u_long addr;
   u_long mask;
{
   struct phaddr *n;

   for (n = uvifs[vifi].uv_addrs; n != NULL; n = n->pa_next) {
      if (addr == n->pa_subnet && mask==n->pa_subnetmask)
         return n;
   }
   return NULL;
}

/*
 * Find the lowest altnet >= (V,A,M) spec
 */
struct phaddr *
next_altnet(vifi, addr, mask, gt)
   vifi_t *vifi;
   u_long  addr;
   u_long  mask;
   int     gt;   /* if true, don't match =, only match > */
{
   struct phaddr *bestn, *n;
   int  i;

   for (i = *vifi; i < numvifs; i++) {
      bestn = NULL;
      for (n = uvifs[i].uv_addrs; n; n=n->pa_next) {
         if ((i > *vifi || n->pa_subnet > addr
           || (n->pa_subnet == addr && n->pa_subnetmask > mask)
           || (n->pa_subnet == addr && n->pa_subnetmask == mask && !gt))
          && (!bestn || n->pa_subnet < bestn->pa_subnet
           || (n->pa_subnet==bestn->pa_subnet 
            && n->pa_subnetmask<bestn->pa_subnetmask)))
            bestn = n;
      }
      if (bestn) {
         *vifi = i;
         return bestn;
      }
   }
   return NULL;
}

/*
 * Find if a specific scoped boundary exists on a Vif
 */
struct vif_acl *
find_boundary(vifi, addr, mask)
   vifi_t vifi;
   u_long addr;
   u_long mask;
{
   struct vif_acl *n;

   for (n = uvifs[vifi].uv_acl; n != NULL; n = n->acl_next) {
      if (addr == n->acl_addr && mask==n->acl_mask)
         return n;
   }
   return NULL;
}

/*
 * Find the lowest boundary >= (V,A,M) spec
 */
struct vif_acl *
next_boundary(vifi, addr, mask, gt)
   vifi_t *vifi;
   u_long  addr;
   u_long  mask;
   int     gt;  /* if true, don't match =, only match > */
{
   struct vif_acl *bestn, *n;
   int  i;

   for (i = *vifi; i < numvifs; i++) {
      bestn = NULL;
      for (n = uvifs[i].uv_acl; n; n=n->acl_next) {
         if ((i > *vifi || n->acl_addr > addr
           || (n->acl_addr == addr && n->acl_mask > mask)
           || (n->acl_addr == addr && n->acl_mask == mask && !gt))
          && (!bestn || n->acl_addr < bestn->acl_addr
           || (n->acl_addr==bestn->acl_addr && n->acl_mask<bestn->acl_mask)))
            bestn = n;
      }
      if (bestn) {
         *vifi = i;
         return bestn;
      }
   }
   return NULL;
}

/*
 * We have a special case of a write here, in that a single write is 
 * sufficient to create the whole row, since all information required
 * is in the OID.
 */
int
write_boundary(action, var_val, var_val_type, var_val_len, statP, name, length)
   int       action;       /*  IN - RESERVE1, RESERVE2, COMMIT, or FREE */
   u_char   *var_val;      /*  IN - input or output buffer space */
   u_char    var_val_type; /*  IN - type of input buffer */
   int       var_val_len;  /*  IN - input and output buffer len */
   u_char   *statP;        /*  IN - pointer to local statistic */
   oid      *name;         /*  IN - pointer to name requested */
   int       length;       /*  IN - number of sub-ids in the name */
{
   vifi_t vifi;
   u_long addr, mask;
   int    variable;
   struct vif_acl *bound;
   int int_value;
   int buffersize = 1000;

   /*
    * object identifier is of form:
    * 1.3.6.1.3.62.1.7.1.4.I.A.A.A.A.M.M.M.M ,  where:
    *       I is dvmrpBoundaryVifIndex at offset 11
    * A.A.A.A is dvmrpBoundaryAddress at offset 12
    * M.M.M.M is dvmrpBoundaryAddressMask at offset 16
    */

   if (length != 20)
      return SNMP_ERR_NOCREATION; /* length error */

   vifi = name[11];
   if (vifi >= numvifs)
      return SNMP_ERR_WRONGVALUE;

   get_address(name, length, &addr, 12);
   get_address(name, length, &mask, 16);
   bound = find_boundary(vifi, addr, mask);

   variable = name[10];

   /* status is the only user-writable variable in this table. */
   switch(variable) {
   case dvmrpBoundaryStatus:
      if (action==RESERVE1) {
         if (var_val_type != ASN_INTEGER)
            return SNMP_ERR_WRONGTYPE;

         if (asn_parse_int(var_val, &buffersize, &var_val_type, &int_value, 
          sizeof(int_value)) == NULL) 
            return SNMP_ERR_WRONGLENGTH;

         switch(int_value) {

         case ENTRY_CREATEANDGO:
            if (bound)   /* check for prior existence */
               return SNMP_ERR_INCONSISTENTVALUE;
            else {
               bound = create_boundary(vifi, addr, mask);
               save_data();
               return (bound)? SNMP_ERR_NOERROR : SNMP_ERR_RESOURCEUNAVAILABLE;
            }

         case ENTRY_NOTINSERVICE:
            return (bound)? SNMP_ERR_WRONGVALUE : SNMP_ERR_INCONSISTENTVALUE;

         case ENTRY_ACTIVE:
            return (bound)? SNMP_ERR_NOERROR : SNMP_ERR_INCONSISTENTVALUE;

         case ENTRY_DESTROY: 
            if (bound) {
               destroy_boundary(vifi,bound);
               save_data();
            }
            return SNMP_ERR_NOERROR;

         case ENTRY_NOTREADY:
         case ENTRY_CREATEANDWAIT:
         default:
            return SNMP_ERR_WRONGVALUE;
         }
      }
      break;

   default:
      return SNMP_ERR_GENERR;
   }

   return SNMP_ERR_NOERROR;
}

/*
 * Implements the Alternate Subnet Table portion of the DVMRP MIB
 */
u_char *
o_dvmrpAltNetTable(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that points here */
    register oid	*name;	    /* IN/OUT - input name requested, output name found */
    register int	*length;    /* IN/OUT - length of input and output oid's */
    int			exact;	    /* IN - TRUE if an exact match was requested. */
    int			*var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set variable, otherwise 0 */
{
    vifi_t     vifi;
    u_long	   addr, mask;
    struct phaddr *altnet;
    oid        newname[MAX_NAME_LEN];
    int        len;

    *write_method = 0;

    /* Copy name OID to new OID */
    bcopy((char *)vp->name, (char *)newname, (int)vp->namelen * sizeof(oid));

    if (exact) {
	    if (*length != vp->namelen + 9)
		return NULL;

      if ((vifi = name[vp->namelen]) >= numvifs)
      return NULL;

      if (!get_address(name, *length, &addr, vp->namelen+1)
       || !get_address(name, *length, &mask, vp->namelen+5))
		return NULL;

      *write_method = 0; /* XXX write_altnet */

      if (!(altnet = find_altnet(vifi, addr, mask))) 
		return NULL;

       bcopy((char *)name, (char *)newname, ((int)*length) * sizeof(oid));
	 } else {
       len = *length;
       if (compare(name, *length, vp->name, vp->namelen) < 0)
          len = vp->namelen;

	    if (len < vp->namelen + 9) { /* get first entry */

         if (len == vp->namelen) {
            vifi = addr = mask = 0;
         } else {
            vifi = name[vp->namelen];
            get_address(name, len, &addr, vp->namelen+1);
            get_address(name, len, &mask, vp->namelen+5);
         }

         altnet = next_altnet(&vifi,addr,mask,0);
         if (!altnet)
            return NULL;
	    } else {  /* get next entry given previous */
		   vifi = name[vp->namelen];
         get_address(name, *length, &addr, vp->namelen+1);
         get_address(name, *length, &mask, vp->namelen+5);

         if (!(altnet = next_altnet(&vifi,addr,mask,1)))
            return NULL;
	    }
		 newname[vp->namelen] = vifi;
       put_address(newname, altnet->pa_subnet,     vp->namelen+1);
       put_address(newname, altnet->pa_subnetmask, vp->namelen+5);
    }

    /* Save new OID */
    *length = vp->namelen + 9;
    bcopy((char *)newname, (char *)name, ((int)*length) * sizeof(oid));
    *var_len = sizeof(long);

    switch (vp->magic) {

   case dvmrpAltNetStatus:
       long_return = 1; /* active */
       return (u_char *) &long_return;

    default:
       ERROR("");
    }
    return NULL;
}

/*
 * Implements the Boundary Table portion of the DVMRP MIB
 */
u_char *
o_dvmrpBoundaryTable(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that points here */
    register oid	*name;	    /* IN/OUT - input name requested, output name found */
    register int	*length;    /* IN/OUT - length of input and output oid's */
    int			exact;	    /* IN - TRUE if an exact match was requested. */
    int			*var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set variable, otherwise 0 */
{
    vifi_t     vifi;
    u_long	   addr, mask;
    struct vif_acl *bound;
    oid        newname[MAX_NAME_LEN];
    int        len;

    *write_method = 0;

    /* Copy name OID to new OID */
    bcopy((char *)vp->name, (char *)newname, (int)vp->namelen * sizeof(oid));

    if (exact) {
	    if (*length != vp->namelen + 9)
		return NULL;

      if ((vifi = name[vp->namelen]) >= numvifs)
      return NULL;

      if (!get_address(name, *length, &addr, vp->namelen+1)
       || !get_address(name, *length, &mask, vp->namelen+5))
		return NULL;

      *write_method = write_boundary;

      if (!(bound = find_boundary(vifi, addr, mask)))
		return NULL;

       bcopy((char *)name, (char *)newname, ((int)*length) * sizeof(oid));
	 } else {
       len = *length;
       if (compare(name, *length, vp->name, vp->namelen) < 0)
          len = vp->namelen;

	    if (len < vp->namelen + 9) { /* get first entry */

         if (len == vp->namelen) {
            vifi = addr = mask = 0;
         } else {
            vifi = name[vp->namelen];
            get_address(name, len, &addr, vp->namelen+1);
            get_address(name, len, &mask, vp->namelen+5);
         }

         bound = next_boundary(&vifi,addr,mask,0);
         if (!bound)
            return NULL;

   		newname[vp->namelen] = vifi;
         put_address(newname, bound->acl_addr, vp->namelen+1);
         put_address(newname, bound->acl_mask, vp->namelen+5);
	    } else {  /* get next entry given previous */
		   vifi = name[vp->namelen];
         get_address(name, *length, &addr, vp->namelen+1);
         get_address(name, *length, &mask, vp->namelen+5);

         if (!(bound = next_boundary(&vifi,addr,mask,1)))
            return NULL;

		   newname[vp->namelen] = vifi;
         put_address(newname, bound->acl_addr, vp->namelen+1);
         put_address(newname, bound->acl_mask, vp->namelen+5);
	    }
    }

    /* Save new OID */
    *length = vp->namelen + 9;
    bcopy((char *)newname, (char *)name, ((int)*length) * sizeof(oid));
    *var_len = sizeof(long);

    switch (vp->magic) {

   case dvmrpBoundaryVifIndex:
       long_return = vifi;
       return (u_char *) &long_return;

   case dvmrpBoundaryStatus:
       long_return = 1; /* active */
       return (u_char *) &long_return;

    default:
       ERROR("");
    }
    return NULL;
}

/*
 * Find the lowest neighbor >= (V,A) spec
 */
struct listaddr *
next_neighbor(vifi, addr)
   vifi_t *vifi;
   u_long  addr;
{
   struct listaddr *bestn, *n;
   int  i;

   for (i = *vifi; i < numvifs; i++) {
      bestn = NULL;
      for (n = uvifs[i].uv_neighbors; n; n=n->al_next) {
         if ((i > *vifi || n->al_addr >= addr)
          && (!bestn || n->al_addr < bestn->al_addr))
            bestn = n;
      }
      if (bestn) {
         *vifi = i;
         return bestn;
      }
   }
   return NULL;
}

/*
 * Find a neighbor, if it exists off a given Vif
 */
struct listaddr *
find_neighbor(vifi, addr)
   vifi_t vifi;
   u_long addr;
{
   struct listaddr *n;

   for (n = uvifs[vifi].uv_neighbors; n != NULL; n = n->al_next) {
      if (addr == n->al_addr)
         return n;
   }
   return NULL;
}

u_char *
o_dvmrpNeighborTable(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that points here */
    register oid	*name;	    /* IN/OUT - input name requested, output name found */
    register int	*length;    /* IN/OUT - length of input and output oid's */
    int			exact;	    /* IN - TRUE if an exact match was requested. */
    int			*var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set variable, otherwise 0 */
{
    vifi_t     vifi;
    u_long     addr, mask;
    struct listaddr *neighbor;
    oid        newname[MAX_NAME_LEN];
    int        len;

    /* Copy name OID to new OID */
    bcopy((char *)vp->name, (char *)newname, (int)vp->namelen * sizeof(oid));

    if (exact) {
	    if (*length != vp->namelen + 5)
		return NULL;

      if ((vifi = name[vp->namelen]) >= numvifs)
      return NULL;

      if (!get_address(name, *length, &addr, vp->namelen+1))
		return NULL;

      if (!(neighbor = find_neighbor(vifi, addr)))
		return NULL;

       bcopy((char *)name, (char *)newname, ((int)*length) * sizeof(oid));
	 } else {
       len = *length;
       if (compare(name, *length, vp->name, vp->namelen) < 0)
          len = vp->namelen;

	    if (len < vp->namelen + 5) { /* get first entry */

         if (len == vp->namelen) {
            vifi = addr = 0;
         } else {
            vifi = name[vp->namelen];
            get_address(name, len, &addr, vp->namelen+1);
         }

         neighbor = next_neighbor(&vifi,addr);
         if (!neighbor)
            return NULL;

   		newname[vp->namelen] = vifi;
         put_address(newname, neighbor->al_addr, vp->namelen+1);
	    } else {  /* get next entry given previous */
		   vifi = name[vp->namelen];
         get_address(name, *length, &addr, vp->namelen+1);

         if (!(neighbor = next_neighbor(&vifi,addr+1)))
            return NULL;

		   newname[vp->namelen] = vifi;
         put_address(newname, neighbor->al_addr, vp->namelen+1);
	    }
    }

    /* Save new OID */
    *length = vp->namelen + 5;
    bcopy((char *)newname, (char *)name, ((int)*length) * sizeof(oid));
    *write_method = 0;
    *var_len = sizeof(long);

    switch (vp->magic) {

   case dvmrpNeighborUpTime: {
       time_t currtime;
       time(&currtime);
       long_return = (currtime - neighbor->al_ctime)*100;
       return (u_char *) &long_return;
   }

   case dvmrpNeighborExpiryTime: 
       long_return = (NEIGHBOR_EXPIRE_TIME - neighbor->al_timer 
        + secs_remaining_offset()) * 100;
       return (u_char *) &long_return;

   case dvmrpNeighborVersion: {
       static char buff[15];

       sprintf(buff, "%d.%d", neighbor->al_pv, neighbor->al_mv);
       *var_len = strlen(buff);
       return (u_char *)buff;
   }

   case dvmrpNeighborGenerationId:
       long_return = neighbor->al_genid;
       return (u_char *) &long_return;

    default:
       ERROR("");
    }
    return NULL;
}

/*
 * Look up the ifIndex for a given local IP address
 */
struct in_ifaddr *  /* OUT: in_ifaddr structure, or null on error */
ipaddr_to_ifindex(ipaddr, ifIndex)
   u_long ipaddr;   /* IN:  local IP address */
   int   *ifIndex;  /* OUT: interface index */
{
    int interface;
static struct in_ifaddr in_ifaddr;

    Interface_Scan_Init();
    for (;;) {
       if (Interface_Scan_Next(&interface, (char *)0, NULL, &in_ifaddr) == 0) 
          return NULL;
    
       if (((struct sockaddr_in *) &(in_ifaddr.ia_addr))->sin_addr.s_addr 
        == ipaddr) {
          *ifIndex = interface;
          return &in_ifaddr;
       }
    }
}

/*
 * Look for a specific group cache entry on a given vif
 */
struct listaddr *  /* OUT: cache structure, or null if not found */
find_cache(grp, vifi)
   u_long grp;     /* IN:  multicast group address */
   vifi_t vifi;    /* IN:  vif index */
{
   struct listaddr *n;

   for (n = uvifs[vifi].uv_groups; n != NULL; n = n->al_next) {
      if (grp == n->al_addr)
         return n;
   }
   return NULL;
}

/*
 * Find the next group cache entry >= (A,V) spec
 */
struct listaddr *
next_cache(addr, vifi)
   u_long  addr;
   vifi_t *vifi;
{
   struct listaddr *bestn=NULL, *n;
   int  i, besti;

   /* Step through all entries looking for the next one */
   for (i = 0; i < numvifs; i++) {
      for (n = uvifs[i].uv_groups; n; n=n->al_next) {
         if ((n->al_addr > addr || (n->al_addr == addr && i >= *vifi))
          && (!bestn || n->al_addr < bestn->al_addr 
           || (n->al_addr == bestn->al_addr && i < besti))) {
            bestn = n;
            besti = i;
         }
      }
   }

   if (bestn) {
      *vifi = besti;
      return bestn;
   }
   return NULL;
}

/*
 * Implements the IGMP Cache Table portion of the IGMP MIB
 */
u_char *
o_igmpCacheTable(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that points here */
    register oid	*name;	    /* IN/OUT - input name requested, output name found */
    register int	*length;    /* IN/OUT - length of input and output oid's */
    int			exact;	    /* IN - TRUE if an exact match was requested. */
    int			*var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set variable, otherwise 0 */
{
    vifi_t     vifi;
    u_long     grp;
    int	      ifIndex;
    struct listaddr *cache;
    oid        newname[MAX_NAME_LEN];
    int        len;
    struct in_ifaddr *in_ifaddr;
    struct in_multi   in_multi, *inm;

    /* Copy name OID to new OID */
    bcopy((char *)vp->name, (char *)newname, (int)vp->namelen * sizeof(oid));

    if (exact) {
	    if (*length != vp->namelen + 5)
		return NULL;

      if ((vifi = name[vp->namelen+4]) >= numvifs)
      return NULL;

      if (!get_address(name, *length, &grp, vp->namelen))
		return NULL;

      if (!(cache = find_cache(grp, vifi)))
		return NULL;

       bcopy((char *)name, (char *)newname, ((int)*length) * sizeof(oid));
	 } else {
       len = *length;
       if (compare(name, *length, vp->name, vp->namelen) < 0)
          len = vp->namelen;

	    if (len < vp->namelen + 5) { /* get first entry */

         if (len == vp->namelen) {
            vifi = grp = 0;
         } else {
            get_address(name, len, &grp, vp->namelen);
            vifi = name[vp->namelen+4];
         }

         cache = next_cache(grp,&vifi);
         if (!cache)
            return NULL;

         put_address(newname, cache->al_addr, vp->namelen);
   		newname[vp->namelen+4] = vifi;
	    } else {  /* get next entry given previous */
         get_address(name, *length, &grp, vp->namelen);
		   vifi = name[vp->namelen+4]+1;

         if (!(cache = next_cache(grp,&vifi)))
            return NULL;

         put_address(newname, cache->al_addr, vp->namelen);
		   newname[vp->namelen+4] = vifi;
	    }
    }

    /* Save new OID */
    *length = vp->namelen + 5;
    bcopy((char *)newname, (char *)name, ((int)*length) * sizeof(oid));
    *write_method = 0;
    *var_len = sizeof(long);

    /* Look up ifIndex given uvifs[vifi].uv_lcl_addr */
    in_ifaddr = ipaddr_to_ifindex(uvifs[vifi].uv_lcl_addr, &ifIndex);

    switch (vp->magic) {

   case igmpCacheSelf: 
       inm = in_ifaddr->ia_multiaddrs;
       while (inm) {
          klookup( (int)inm, (char *)&in_multi, sizeof(in_multi));

          if (in_multi.inm_addr.s_addr == cache->al_addr) {
             long_return = 1; /* true */
             return (u_char *) &long_return;
          }

          inm = in_multi.inm_next;
       }
       long_return = 2; /* false */
       return (u_char *) &long_return;

   case igmpCacheLastReporter:
       return (u_char *) &cache->al_genid;

   case igmpCacheUpTime: {
      time_t currtime;
      time(&currtime);
      long_return = (currtime - cache->al_ctime)*100;
      return (u_char *) &long_return;
   }

   case igmpCacheExpiryTime: 
       long_return = secs_remaining(cache->al_timerid)*100;
       return (u_char *) &long_return;

   case igmpCacheStatus: 
       long_return = 1;
       return (u_char *) &long_return;

    default:
       ERROR("");
    }
    return NULL;
}

/*
 * Implements the IGMP Interface Table portion of the IGMP MIB
 */
u_char *
o_igmpInterfaceTable(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that points here */
    register oid	*name;	    /* IN/OUT - input name requested, output name found */
    register int	*length;    /* IN/OUT - length of input and output oid's */
    int			exact;	    /* IN - TRUE if an exact match was requested. */
    int			*var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set variable, otherwise 0 */
{
    oid			newname[MAX_NAME_LEN];
    register int	ifnum;
    int result;
static struct sioc_vif_req v_req;

    /* Copy name OID to new OID */
    bcopy((char *)vp->name, (char *)newname, (int)vp->namelen * sizeof(oid));

    /* find "next" interface */
    for(ifnum = 0; ifnum < numvifs; ifnum++){
       if (!(uvifs[ifnum].uv_flags & VIFF_QUERIER))
           continue;
       newname[vp->namelen] = (oid)ifnum;
       result = compare(name, *length, newname, (int)vp->namelen + 1);
       if ((exact && (result == 0)) || (!exact && (result < 0)))
          break;
    }
    if (ifnum >= numvifs)
       return NULL;

    /* Save new OID */
    bcopy((char *)newname, (char *)name, ((int)vp->namelen + 1) * sizeof(oid));
    *length = vp->namelen + 1;
    *write_method = 0;
    *var_len = sizeof(long);

    switch (vp->magic){

	case igmpInterfaceQueryInterval:
		long_return = GROUP_QUERY_INTERVAL;
      return (u_char *) &long_return;

	case igmpInterfaceStatus:
		long_return = 1; /* active */
      return (u_char *) &long_return;

	default:
	    ERROR("");
    }
    return NULL;
}

/*
 * Given a virtual interface number, make sure we have the current
 * kernel information for that Vif.
 */
refresh_vif(v_req, ifnum)
   struct sioc_vif_req *v_req;
   int ifnum;
{
   static   int lastq = -1;

   if (quantum!=lastq || v_req->vifi != ifnum) {
       lastq = quantum;
       v_req->vifi = ifnum;
       if (ioctl(udp_socket, SIOCGETVIFCNT, (char *)v_req) < 0)
          v_req->icount = v_req->ocount = v_req->ibytes = v_req->obytes = 0;
   }
}

void
save_data()
{
   vifi_t          vifi;
   struct uvif    *v;
   struct vif_acl *n;
   struct phaddr  *a;
   time_t          tm;
extern char sysName[1024], sysContact[128], version_descr[128], sysLocation[128];
extern char *configfilename;
   char  backup[1024];
   struct stat st;
   FILE *fp;

   /* If there is no backup file, then backup the original */
   sprintf(backup, "%s.orig", configfilename);
   if (stat(backup,&st)) {
      link(configfilename, backup);
      unlink(configfilename);
   }

   /* Open config file for writing */
   if ((fp = fopen(configfilename, "w"))==NULL) {
      log(LOG_ERR, errno, "couldn't save %s", configfilename);
      return;
   }

   /* Write header */
   time(&tm);
   fprintf(fp,"# This mrouted.conf file was generated automatically by mrouted\n");
   fprintf(fp,"#\n");

   /* Write out all vifs */
   fprintf(fp,"# vif information\n");
   for (vifi=0; vifi<numvifs; vifi++) {
      v = &uvifs[vifi];
      if (v->uv_flags & VIFF_TUNNEL) {
         fprintf(fp,"tunnel %s %s%s metric %d threshold %d",
          inet_fmt(v->uv_lcl_addr, s1),
          inet_fmt(v->uv_rmt_addr, s2),
          (v->uv_flags & VIFF_DISABLED)? " disable" : "",
          v->uv_metric, v->uv_threshold);
         if (v->uv_rate_limit!=500)
            fprintf(fp," rate_limit %d", v->uv_rate_limit);
         fprintf(fp,"\n");
      } else {
         fprintf(fp,"phyint %s%s metric %d threshold %d",
          inet_fmt(v->uv_lcl_addr, s1),
          (v->uv_flags & VIFF_DISABLED)? " disable" : "",
          v->uv_metric, v->uv_threshold);
         if (v->uv_rate_limit)
            fprintf(fp," rate_limit %d", v->uv_rate_limit);
         fprintf(fp,"\n");
      }

      /* Write out all boundaries on this vif */
      for (n = uvifs[vifi].uv_acl; n != NULL; n = n->acl_next) {
         if ((ntohl(n->acl_addr) & 0xff000000) == 0xef000000)
            fprintf(fp,"   boundary %s/%u\n", inet_fmt(n->acl_addr,s1), 
             33 - ffs(ntohl(n->acl_mask)));
      }

      /* Write out all altnets if this is a subnet */
      for (a = uvifs[vifi].uv_addrs; a != NULL; a = a->pa_next)
         fprintf(fp,"   altnet %s\n", inet_fmts(a->pa_subnet,a->pa_subnetmask,
          s1));
   }

   /* Now dump generic SNMP information */
   fprintf(fp,"#\n");
   fprintf(fp,"# SNMP information\n");
   fprintf(fp,"sysName \"%s\"\n", sysName);
   fprintf(fp,"sysContact \"%s\"\n", sysContact);
   fprintf(fp,"sysVersion \"%s\"\n", version_descr);
   fprintf(fp,"sysLocation \"%s\"\n", sysLocation);

   fclose(fp);
}

/*
 * Code to allow remote vif (re)configuration.  Note that only tunnels
 * can be created, but any vif may be modified.
 *
 * This routine is called 3 times for each variable to be set.  Action will
 * be RESERVE1, then RESERVE2, and finally either COMMIT or FREE depending
 * on whether there were errors in the first two passes.
 */
int
write_vif(action, var_val, var_val_type, var_val_len, statP, name, length)
   int       action;       /*  IN - RESERVE1, RESERVE2, COMMIT, or FREE */
   u_char   *var_val;      /*  IN - input or output buffer space */
   u_char    var_val_type; /*  IN - type of input buffer */
   int       var_val_len;  /*  IN - input and output buffer len */
   u_char   *statP;        /*  IN - pointer to local statistic */
   oid      *name;         /*  IN - pointer to name requested */
   int       length;       /*  IN - number of sub-ids in the name */
{
   vifi_t vifi, vifi2;
   int    variable;
   int    int_value;
   u_long addr_value, localaddr, remoteaddr;
   int    buffersize = 1000;
   int    current_state;
   char   buf[8];
   int    bufsz;
   struct ifreq *ifr;
   struct ifreq ffr;
   struct ifreq  ifbuf[32];
   struct ifconf ifc;
   struct uvif *v;
   static int lastq = -1;
   static struct uvif newvifs[MAXVIFS];

   /* Reset stored values at start of new PDU */
   if (quantum!=lastq) {
       lastq = quantum;
       memcpy(newvifs, uvifs, MAXVIFS*sizeof(struct uvif));
   }

   /*
    * object identifier in 'name' is of the form:
    * 1.3.6.1.3.62.1.1.3.1.X.I ,  where:
    *       X is variable at offset 10
    *       I is dvmrpBoundaryVifIndex at offset 11
    * -or-
    * 1.3.6.1.4.1.9.10.2.1.1.4.1.X.I , where:
    *       X is variable at offset 13
    *       I is ipMRouteInterfaceIfIndex at offset 14
    */

   if (length==12) {        /* inside dvmrp mib */
      variable = name[10];
      vifi     = name[11];
   } else if (length==15) { /* inside multicast mib */
      variable = ipMRouteInterfaceTtl;
      vifi     = name[14];
   } else
      return SNMP_ERR_NOCREATION; /* length error */

   /* Determine current row status */
   if (vifi<0 || vifi>=numvifs)
      current_state = ENTRY_DESTROY; /* doesn't exist */
   else if (!(uvifs[vifi].uv_flags & VIFF_DISABLED)) 
      current_state = ENTRY_ACTIVE;
   else if (!uvifs[vifi].uv_lcl_addr 
       || ((uvifs[vifi].uv_flags & VIFF_TUNNEL) && !uvifs[vifi].uv_rmt_addr))
      current_state = ENTRY_NOTREADY;
   else
      current_state = ENTRY_NOTINSERVICE;

   /* Check type and get value */
   if (variable==dvmrpVInterfaceLocalAddress
    || variable==dvmrpVInterfaceRemoteAddress) {
      if (var_val_type != ASN_OCTET_STR) 
         return SNMP_ERR_WRONGTYPE;
      bufsz = 8;
      asn_parse_string(var_val, &buffersize, &var_val_type, buf, &bufsz);
      addr_value = *((u_long *) buf);
   } else {
      if (var_val_type != ASN_INTEGER)
         return SNMP_ERR_WRONGTYPE;
      if (asn_parse_int(var_val, &buffersize, &var_val_type, &int_value, 
       sizeof(int_value)) == NULL)     
         return SNMP_ERR_WRONGLENGTH;
   }

   /*
    * Can only ever do sets to actual vifs plus one potential,
    * EXCEPT can legally set dvmrpVInterfaceStatus to destoy(6).
    */
   if (vifi<0 || vifi>numvifs) {
      if (variable==dvmrpVInterfaceStatus && int_value==6)
         return SNMP_ERR_NOERROR;
      return SNMP_ERR_INCONSISTENTVALUE;
   }
   if (vifi>=MAXVIFS)
      return SNMP_ERR_RESOURCEUNAVAILABLE;

   switch(action) {
   case RESERVE1: 
      /* The type and value should be checked during this pass.  If any 
       * other variables in the MIB depend on this variable, this variable 
       * will be stored away (but *not* committed!) in a place where it 
       * can be found by a call to write_vif for a dependent variable, 
       * even in the same PDU.
       */

      /* Check value */
      switch(variable) {
      case dvmrpVInterfaceLocalAddress:

         ifc.ifc_buf = (char *)ifbuf;
         ifc.ifc_len = sizeof(ifbuf);
         if (ioctl(udp_socket, SIOCGIFCONF, (char *)&ifc) < 0)
             log(LOG_ERR, errno, "ioctl SIOCGIFCONF");

         ifr = ifconfaddr(&ifc, addr_value);
         if (ifr == 0)
            return SNMP_ERR_INCONSISTENTVALUE; /* Local address is not mine */

         strncpy(ffr.ifr_name, ifr->ifr_name, IFNAMSIZ);
         if (ioctl(udp_socket, SIOCGIFFLAGS, (char *)&ffr)<0)
            return SNMP_ERR_INCONSISTENTVALUE;
         if (ffr.ifr_flags & IFF_LOOPBACK)
            return SNMP_ERR_INCONSISTENTVALUE; /* Local addr is loopback */

         ifc.ifc_buf = (char *)ifbuf;
         ifc.ifc_len = sizeof(ifbuf);
         if (ioctl(udp_socket, SIOCGIFCONF, (char *)&ifc) < 0)
             log(LOG_ERR, errno, "ioctl SIOCGIFCONF");

         newvifs[vifi].uv_lcl_addr = addr_value;
         ifr = ifconfaddr(&ifc, addr_value);
         strncpy(newvifs[vifi].uv_name, ifr->ifr_name, IFNAMSIZ);

         return SNMP_ERR_NOERROR;

      case dvmrpVInterfaceRemoteAddress:
         ifc.ifc_buf = (char *)ifbuf;
         ifc.ifc_len = sizeof(ifbuf);
         if (ioctl(udp_socket, SIOCGIFCONF, (char *)&ifc) < 0)
             log(LOG_ERR, errno, "ioctl SIOCGIFCONF");

         if (ifconfaddr(&ifc, addr_value) != 0)
            return SNMP_ERR_INCONSISTENTVALUE; /* Remote address is mine */
         newvifs[vifi].uv_rmt_addr = addr_value;
         return SNMP_ERR_NOERROR;

      case ipMRouteInterfaceTtl:      
         if (int_value < 1 || int_value > 255)
            return SNMP_ERR_WRONGVALUE;
         newvifs[vifi].uv_threshold = int_value;
         return SNMP_ERR_NOERROR;

      case dvmrpVInterfaceMetric:      
         if (int_value < 1 || int_value >= UNREACHABLE)
            return SNMP_ERR_WRONGVALUE;
         newvifs[vifi].uv_metric = int_value;
         return SNMP_ERR_NOERROR;

      case dvmrpVInterfaceRateLimit:  
         if (int_value<0) 
            return SNMP_ERR_WRONGVALUE;
         newvifs[vifi].uv_rate_limit = int_value;
         return SNMP_ERR_NOERROR;

      case dvmrpVInterfaceStatus:    
         switch(int_value) {
         case ENTRY_CREATEANDGO:
         case ENTRY_CREATEANDWAIT:
            if (vifi!=numvifs)
               return SNMP_ERR_INCONSISTENTVALUE;

            /* Set default values */
            v = &newvifs[numvifs];
            v->uv_flags      = VIFF_TUNNEL;
            v->uv_metric     = DEFAULT_METRIC;
            v->uv_rate_limit = DEFAULT_TUN_RATE_LIMIT;
            v->uv_threshold  = DEFAULT_THRESHOLD;
            v->uv_lcl_addr   = 0;
            v->uv_rmt_addr   = 0;
            v->uv_subnet     = 0;
            v->uv_subnetmask = 0;
            v->uv_subnetbcast= 0;
            v->uv_groups     = NULL;
            v->uv_neighbors  = NULL;
            v->uv_acl        = NULL;
            v->uv_addrs      = NULL;

            if (int_value==ENTRY_CREATEANDWAIT)
               v->uv_flags |= VIFF_DISABLED;

            return SNMP_ERR_NOERROR;

         case ENTRY_ACTIVE:
         case ENTRY_NOTINSERVICE:
            if (current_state==ENTRY_DESTROY)
               return SNMP_ERR_INCONSISTENTVALUE;
            return SNMP_ERR_NOERROR;

         case ENTRY_DESTROY:
            /*
             * Don't allow deleting vifs before the last one, and 
             * don't allow deleting subnets.
             */
            if (vifi < numvifs-1 
             || (vifi==numvifs-1 && !(uvifs[vifi].uv_flags & VIFF_TUNNEL)))
               return SNMP_ERR_INCONSISTENTVALUE;
            return SNMP_ERR_NOERROR;

         default:
            return SNMP_ERR_WRONGVALUE;
         }
      }
      return SNMP_ERR_GENERR;

   case RESERVE2:
      /* If this variable is dependent on any other variables, it will 
       * check them now.  It must check to see if any non-committed values 
       * have been stored for variables in the same PDU that it depends on.  
       * Sometimes resources will need to be reserved in the first two 
       * passes to guarantee that the operation can proceed during the 
       * third pass.
       */
      switch(variable) {
      case dvmrpVInterfaceLocalAddress:
      case dvmrpVInterfaceRemoteAddress:
         if (vifi==numvifs) {
            localaddr  = newvifs[vifi].uv_lcl_addr;
            remoteaddr = newvifs[vifi].uv_rmt_addr;
         } else {
            localaddr  = (variable==dvmrpVInterfaceLocalAddress)? 
             addr_value : newvifs[vifi].uv_lcl_addr;
            remoteaddr = (variable==dvmrpVInterfaceRemoteAddress)? 
             addr_value : newvifs[vifi].uv_rmt_addr;
         }

         for (vifi2=0,v=newvifs; vifi2<numvifs; ++vifi2,++v) {
            if (vifi2==vifi) continue;
            if (v->uv_flags & VIFF_TUNNEL) {
               if (!v->uv_lcl_addr || !v->uv_rmt_addr) continue; /* notReady */
               if (localaddr==v->uv_lcl_addr && remoteaddr==v->uv_rmt_addr)
                  return SNMP_ERR_INCONSISTENTVALUE; /* Duplicate tunnel */
            } else if (!(v->uv_flags & VIFF_DISABLED)) {
               if ((remoteaddr & v->uv_subnetmask) == v->uv_subnet)
                  return SNMP_ERR_INCONSISTENTVALUE; /* Unnecessary tunnel */
            }
         }
         return SNMP_ERR_NOERROR;

      case dvmrpVInterfaceStatus:    
         switch(int_value) {
         case ENTRY_CREATEANDGO:
         case ENTRY_ACTIVE:
         case ENTRY_NOTINSERVICE:
            if (!newvifs[vifi].uv_lcl_addr 
             || ((newvifs[vifi].uv_flags & VIFF_TUNNEL) 
              && !newvifs[vifi].uv_rmt_addr))
                  return SNMP_ERR_INCONSISTENTVALUE; /* Missing values */
            return SNMP_ERR_NOERROR;

         default:
            return SNMP_ERR_NOERROR;
         }

      default:
         return SNMP_ERR_NOERROR;
      }

   case COMMIT:
      /*
       * It is now that the values should be written. 
       */
      switch(variable) {
      case dvmrpVInterfaceLocalAddress:
         if (addr_value==uvifs[vifi].uv_lcl_addr)
            return SNMP_ERR_NOERROR; /* no change */
        
         if (current_state == ENTRY_ACTIVE)
            stop_vif(vifi);

         ifc.ifc_buf = (char *)ifbuf;
         ifc.ifc_len = sizeof(ifbuf);
         if (ioctl(udp_socket, SIOCGIFCONF, (char *)&ifc) < 0)
             log(LOG_ERR, errno, "ioctl SIOCGIFCONF");

         uvifs[vifi].uv_lcl_addr = addr_value;
         ifr = ifconfaddr(&ifc, addr_value);
         strncpy(v->uv_name, ifr->ifr_name, IFNAMSIZ);

         if (current_state == ENTRY_ACTIVE) {
            strncpy(ffr.ifr_name, ifr->ifr_name, IFNAMSIZ);
            if (ioctl(udp_socket, SIOCGIFFLAGS, (char *)&ffr)<0)
               return SNMP_ERR_INCONSISTENTVALUE; /* should never happen */

            if (!(ffr.ifr_flags & IFF_UP)) {
               v->uv_flags |= VIFF_DOWN;
               vifs_down = TRUE;
            }

            start_vif(vifi);
         }
         save_data();
         return SNMP_ERR_NOERROR;

      case dvmrpVInterfaceRemoteAddress:
         if (addr_value==uvifs[vifi].uv_rmt_addr)
            return SNMP_ERR_NOERROR; /* no change */

         /* Temporarily take it out of service */
         if (current_state == ENTRY_ACTIVE)
            stop_vif(vifi);

         /* Make the change */
         uvifs[vifi].uv_rmt_addr = addr_value;

         /* If it was in service before, restore it */
         if (current_state == ENTRY_ACTIVE) {
            ifc.ifc_buf = (char *)ifbuf;
            ifc.ifc_len = sizeof(ifbuf);
            if (ioctl(udp_socket, SIOCGIFCONF, (char *)&ifc) < 0)
                log(LOG_ERR, errno, "ioctl SIOCGIFCONF");

            ifr = ifconfaddr(&ifc, uvifs[vifi].uv_lcl_addr);
            strncpy(v->uv_name, ifr->ifr_name, IFNAMSIZ);

            strncpy(ffr.ifr_name, ifr->ifr_name, IFNAMSIZ);
            if (ioctl(udp_socket, SIOCGIFFLAGS, (char *)&ffr)<0)
               return SNMP_ERR_INCONSISTENTVALUE; /* should never happen */

            if (!(ffr.ifr_flags & IFF_UP)) {
               v->uv_flags |= VIFF_DOWN;
               vifs_down = TRUE;
            }

            start_vif(vifi);
         }
         save_data();
         return SNMP_ERR_NOERROR;

      case dvmrpVInterfaceMetric:      

         /*
          * This doesn't affect kernel at all.  The local routing table will
          * be recalculated when a neighbor report is received on this vif.
          * Also, this metric will be automatically used in our reports to
          * neighbors.
          */
         uvifs[vifi].uv_metric = int_value;
         save_data();
         return SNMP_ERR_NOERROR;

      case ipMRouteInterfaceTtl:      
         if (int_value==uvifs[vifi].uv_threshold)
            return SNMP_ERR_NOERROR; /* no change */

         /* When threshold changes, we must update the kernel entry */
         if (current_state == ENTRY_ACTIVE)
            stop_vif(vifi);
         uvifs[vifi].uv_threshold = int_value;
         if (current_state == ENTRY_ACTIVE)
            start_vif(vifi);
         save_data();
         return SNMP_ERR_NOERROR;

      case dvmrpVInterfaceRateLimit:  
         if (int_value==uvifs[vifi].uv_rate_limit)
            return SNMP_ERR_NOERROR; /* no change */

         /* When rate limit changes, we must update the kernel entry */
         if (current_state == ENTRY_ACTIVE)
            stop_vif(vifi);
         uvifs[vifi].uv_rate_limit = int_value;
         if (current_state == ENTRY_ACTIVE)
            start_vif(vifi);
         save_data();
         return SNMP_ERR_NOERROR;

      case dvmrpVInterfaceStatus:
         if (int_value==current_state)
            return SNMP_ERR_NOERROR; /* no change */
         switch(int_value) {

         /*
          * We have passed all legality checks, so we know the tunnel
          * is valid.  Try to create it.
          */
         case ENTRY_CREATEANDGO:
            /* copy new vif info to actual vif info */
            memcpy(&uvifs[vifi], &newvifs[vifi], sizeof(struct uvif));
            if (vifi==numvifs) {
               uvifs[vifi].uv_flags |= VIFF_DISABLED;
               numvifs++;      /* commit the new row */
            }

            /* fall through into ENTRY_ACTIVE case below... */

         case ENTRY_ACTIVE:
            if (current_state==ENTRY_NOTREADY) {
               /* copy new vif info to actual vif info */
               memcpy(&uvifs[vifi], &newvifs[vifi], sizeof(struct uvif));
            }

            if (uvifs[vifi].uv_flags & VIFF_DISABLED) {
               uvifs[vifi].uv_flags &= ~VIFF_DISABLED;

               /* set the vifs_down flag to remind us to check up state */
               uvifs[vifi].uv_flags |=  VIFF_DOWN;
               vifs_down = TRUE;
            }
            save_data();
            return SNMP_ERR_NOERROR;

         case ENTRY_CREATEANDWAIT:
            /* copy new vif info to actual vif info */
            memcpy(&uvifs[vifi], &newvifs[vifi], sizeof(struct uvif));
            if (vifi==numvifs) {
               uvifs[vifi].uv_flags |= VIFF_DISABLED;
               numvifs++;      /* commit the new row */
            }

            /* fall through into ENTRY_NOTINSERVICE case below... */

         case ENTRY_NOTINSERVICE:
         case ENTRY_DESTROY:
            if (current_state==ENTRY_NOTREADY) {
               /* copy new vif info to actual vif info */
               memcpy(&uvifs[vifi], &newvifs[vifi], sizeof(struct uvif));
            }

            if (current_state==ENTRY_ACTIVE) {
               stop_vif(vifi);
               log(LOG_INFO, 0, 
                "%s has been disabled; vif #%u taken out of service",
                uvifs[vifi].uv_name, vifi);
            }
            if (vifi>=0 && vifi<MAXVIFS)
               uvifs[vifi].uv_flags |=  VIFF_DISABLED;

            if (vifi==numvifs-1 && int_value==ENTRY_DESTROY)
               numvifs--; /* destroy the vif */

            save_data();
            return SNMP_ERR_NOERROR;

         }
         return SNMP_ERR_NOERROR;
      }
      break;
   }

   return SNMP_ERR_NOERROR;
}

/*
 * Implements the Multicast Routing Interface Table portion of the Multicast MIB
 */
u_char *
o_ipMRouteInterfaceTable(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that points here */
    register oid	*name;	    /* IN/OUT - input name requested, output name found */
    register int	*length;    /* IN/OUT - length of input and output oid's */
    int			exact;	    /* IN - TRUE if an exact match was requested. */
    int			*var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set variable, otherwise 0 */
{
    oid			newname[MAX_NAME_LEN];
    register int	ifnum;
    int result;
static struct sioc_vif_req v_req;

    *write_method = write_vif;

    /* Copy name OID to new OID */
    bcopy((char *)vp->name, (char *)newname, (int)vp->namelen * sizeof(oid));

    /* find "next" interface */
    for(ifnum = 0; ifnum < numvifs; ifnum++){
       newname[vp->namelen] = (oid)ifnum;
       result = compare(name, *length, newname, (int)vp->namelen + 1);
       if ((exact && (result == 0)) || (!exact && (result < 0)))
          break;
    }
    if (ifnum >= numvifs)
       return NULL;

    /* Save new OID */
    bcopy((char *)newname, (char *)name, ((int)vp->namelen + 1) * sizeof(oid));
    *length = vp->namelen + 1;
    *var_len = sizeof(long);

    switch (vp->magic){

   case ipMRouteInterfaceTtl:
       long_return = uvifs[ifnum].uv_threshold;
       return (u_char *) &long_return;

   case dvmrpVInterfaceType:
      if (uvifs[ifnum].uv_flags & VIFF_SRCRT)
         long_return = 2;
      else if (uvifs[ifnum].uv_flags & VIFF_TUNNEL)
         long_return = 1;
      else if (uvifs[ifnum].uv_flags & VIFF_QUERIER)
         long_return = 3;
      else                               /* SUBNET */
         long_return = 4;
      return (u_char *) &long_return;

   case dvmrpVInterfaceOperState:
      if (uvifs[ifnum].uv_flags & VIFF_DISABLED)
         long_return = 2;
      else if ((uvifs[ifnum].uv_flags & VIFF_DOWN)
       || ((uvifs[ifnum].uv_flags & VIFF_TUNNEL) && (uvifs[ifnum].uv_neighbors==NULL)))
         long_return = 2;
      else /* UP */
         long_return = 1;
      return (u_char *) &long_return;

   case dvmrpVInterfaceLocalAddress: 
      return (u_char *) &uvifs[ifnum].uv_lcl_addr;

   case dvmrpVInterfaceRemoteAddress: 
      return (u_char *) ((uvifs[ifnum].uv_flags & VIFF_TUNNEL) ?
         &uvifs[ifnum].uv_rmt_addr :
         &uvifs[ifnum].uv_subnet);

   case dvmrpVInterfaceRemoteSubnetMask:
      return (u_char *) &uvifs[ifnum].uv_subnetmask;

   case dvmrpVInterfaceMetric:
       long_return = uvifs[ifnum].uv_metric;
       return (u_char *) &long_return;

   case dvmrpVInterfaceRateLimit:
       long_return = uvifs[ifnum].uv_rate_limit;
       return (u_char *) &long_return;

   case dvmrpVInterfaceInPkts:
       refresh_vif(&v_req, ifnum);
       long_return = v_req.icount;
       return (u_char *) &long_return;

   case dvmrpVInterfaceOutPkts:
       refresh_vif(&v_req, ifnum);
       long_return = v_req.ocount;
       return (u_char *) &long_return;

   case dvmrpVInterfaceInOctets:
       refresh_vif(&v_req, ifnum);
       long_return = v_req.ibytes;
       return (u_char *) &long_return;

   case dvmrpVInterfaceOutOctets:
       refresh_vif(&v_req, ifnum);
       long_return = v_req.obytes;
       return (u_char *) &long_return;

   case dvmrpVInterfaceStatus:
      if (!(uvifs[ifnum].uv_flags & VIFF_DISABLED)) 
         long_return = ENTRY_ACTIVE;
      else if (!uvifs[ifnum].uv_lcl_addr 
       || ((uvifs[ifnum].uv_flags & VIFF_TUNNEL) && !uvifs[ifnum].uv_rmt_addr))
         long_return = ENTRY_NOTREADY;
      else
         long_return = ENTRY_NOTINSERVICE;
      return (u_char *) &long_return;

	default:
	    ERROR("");
    }
    return NULL;
}

/*
 * Implements the DVMRP Route Table portion of the DVMRP MIB
 */
u_char *
o_dvmrpRouteTable(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that points here */
    register oid	*name;	    /* IN/OUT - input name requested, output name found */
    register int	*length;    /* IN/OUT - length of input and output oid's */
    int			exact;	    /* IN - TRUE if an exact match was requested. */
    int			*var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set variable, otherwise 0 */
{
    u_long src, mask;
    oid        newname[MAX_NAME_LEN];
    int        len;
    struct rtentry *rt = NULL;

    /* Copy name OID to new OID */
    bcopy((char *)vp->name, (char *)newname, (int)vp->namelen * sizeof(oid));

    if (exact) {
	    if (*length != vp->namelen + 8)
		return NULL;

      if (!get_address(name, *length, &src, vp->namelen)
       || !get_address(name, *length, &mask, vp->namelen+4))
		return NULL;

      if (!(rt = snmp_find_route(src, mask)))
		return NULL;

       bcopy((char *)name, (char *)newname, ((int)*length) * sizeof(oid));
	 } else {
       len = *length;
       if (compare(name, *length, vp->name, vp->namelen) < 0)
          len = vp->namelen;

	    if (len < vp->namelen + 8) { /* get first entry */

         if (len == vp->namelen) {
            src = mask = 0;
         } else {
            get_address(name, len, &src, vp->namelen);
            get_address(name, len, &mask, vp->namelen+4);
         }

         if (!next_route(&rt,src,mask)) /* Get first entry */
            return NULL;

         put_address(newname, rt->rt_origin    , vp->namelen);
         put_address(newname, rt->rt_originmask, vp->namelen+4);
	    } else {  /* get next entry given previous */
         get_address(name, *length, &src,  vp->namelen);
         get_address(name, *length, &mask, vp->namelen+4);

         if (!next_route(&rt, src,mask))
            return NULL;

         put_address(newname, rt->rt_origin,     vp->namelen);
         put_address(newname, rt->rt_originmask, vp->namelen+4);
	    }
    }

    /* Save new OID */
    *length = vp->namelen + 8;
    bcopy((char *)newname, (char *)name, ((int)*length) * sizeof(oid));
    *write_method = 0;
    *var_len = sizeof(long);

    switch (vp->magic) {

      case dvmrpRouteUpstreamNeighbor: 
         return (u_char *) &rt->rt_gateway;

      case dvmrpRouteInVifIndex:
         long_return = rt->rt_parent;
         return (u_char *) &long_return;

      case dvmrpRouteMetric:
         long_return = rt->rt_metric;
         return (u_char *) &long_return;

      case dvmrpRouteExpiryTime:
         long_return = (ROUTE_EXPIRE_TIME - rt->rt_timer 
          + secs_remaining_offset()) * 100;
         return (u_char *) &long_return;

    default:
       ERROR("");
    }
    return NULL;
}

/*
 * Implements the DVMRP Routing Next Hop Table portion of the DVMRP MIB
 */
u_char *
o_dvmrpRouteNextHopTable(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that points here */
    register oid	*name;	    /* IN/OUT - input name requested, output name found */
    register int	*length;    /* IN/OUT - length of input and output oid's */
    int			exact;	    /* IN - TRUE if an exact match was requested. */
    int			*var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set variable, otherwise 0 */
{
    u_long     src, mask;
    vifi_t     vifi;
    struct rtentry *rt = NULL;
    oid        newname[MAX_NAME_LEN];
    int        len;

    /* Copy name OID to new OID */
    bcopy((char *)vp->name, (char *)newname, (int)vp->namelen * sizeof(oid));

    if (exact) {
	    if (*length != vp->namelen + 9)
		return NULL;

      if (!get_address(name, *length, &src, vp->namelen)
       || !get_address(name, *length, &mask, vp->namelen+4)
       || (!(rt=snmp_find_route(src,mask))))
		return NULL;

      vifi = name[vp->namelen+8];
      if (!(VIFM_ISSET(vifi, rt->rt_children)))
      return NULL;

       bcopy((char *)name, (char *)newname, ((int)*length) * sizeof(oid));
	 } else {
       len = *length;
       if (compare(name, *length, vp->name, vp->namelen) < 0)
          len = vp->namelen;

	    if (len < vp->namelen + 9) { /* get first entry */

         get_address(name, len, &src,  vp->namelen);
         get_address(name, len, &mask, vp->namelen+4);

         /* Find first child vif */
         vifi=0;
         if (!next_route_child(&rt, src, mask, &vifi))
            return NULL;

         put_address(newname, rt->rt_origin,     vp->namelen);
         put_address(newname, rt->rt_originmask, vp->namelen+4);
   		newname[vp->namelen+8] = vifi;
	    } else {  /* get next entry given previous */
		   vifi = name[vp->namelen+8] + 1;
         if (!get_address(name, *length, &src,  vp->namelen)
          || !get_address(name, *length, &mask, vp->namelen+4)
          || !next_route_child(&rt, src, mask, &vifi))
            return NULL;

         put_address(newname, rt->rt_origin,     vp->namelen);
         put_address(newname, rt->rt_originmask, vp->namelen+4);
		   newname[vp->namelen+8] = vifi;
	    }
    }

    /* Save new OID */
    *length = vp->namelen + 9;
    bcopy((char *)newname, (char *)name, ((int)*length) * sizeof(oid));
    *write_method = 0;
    *var_len = sizeof(long);

    switch (vp->magic) {

    case dvmrpRouteNextHopType:
       long_return = (VIFM_ISSET(vifi, rt->rt_leaves))? 1 : 2;
       return (u_char *) &long_return;

    default:
       ERROR("");
    }
    return NULL;
}

/*
 * Implements the IP Multicast Route Table portion of the Multicast MIB
 */
u_char *
o_ipMRouteTable(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that points here */
    register oid	*name;	    /* IN/OUT - input name requested, output name found */
    register int	*length;    /* IN/OUT - length of input and output oid's */
    int			exact;	    /* IN - TRUE if an exact match was requested. */
    int			*var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set variable, otherwise 0 */
{
    u_long src, grp, mask;
    struct gtable *gt = NULL;
    struct stable *st = NULL;
static struct sioc_sg_req sg_req;
    oid        newname[MAX_NAME_LEN];
    int        len;

    /* Copy name OID to new OID */
    bcopy((char *)vp->name, (char *)newname, (int)vp->namelen * sizeof(oid));

    if (exact) {
	    if (*length != vp->namelen + 12)
		return NULL;

      if (!get_address(name, *length, &grp,  vp->namelen)
       || !get_address(name, *length, &src,  vp->namelen+4)
       || !get_address(name, *length, &mask, vp->namelen+8)
       || (mask != 0xFFFFFFFF) /* we keep sources now, not subnets */
       || !(gt = find_grp(grp))
       || !(st = find_grp_src(gt,src)))
		return NULL;

       bcopy((char *)name, (char *)newname, ((int)*length) * sizeof(oid));
	 } else {
       len = *length;
       if (compare(name, *length, vp->name, vp->namelen) < 0)
          len = vp->namelen;

	    if (len < vp->namelen + 12) { /* get first entry */

         get_address(name, len, &grp,  vp->namelen);
         get_address(name, len, &src,  vp->namelen+4);
         get_address(name, len, &mask, vp->namelen+8);

         if (!next_grp_src_mask(&gt,&st,grp,src,mask)) /* Get first entry */
            return NULL;

         put_address(newname, gt->gt_mcastgrp, vp->namelen);
         put_address(newname, st->st_origin,   vp->namelen+4);
         put_address(newname, 0xFFFFFFFF,      vp->namelen+8);
	    } else {  /* get next entry given previous */
         get_address(name, *length, &grp , vp->namelen);
         get_address(name, *length, &src , vp->namelen+4);
         get_address(name, *length, &mask, vp->namelen+8);

         if (!next_grp_src_mask(&gt, &st, grp,src,mask))
            return NULL;

         put_address(newname, gt->gt_mcastgrp, vp->namelen);
         put_address(newname, st->st_origin,   vp->namelen+4);
         put_address(newname, 0xFFFFFFFF,      vp->namelen+8);
	    }
    }

    /* Save new OID */
    *length = vp->namelen + 12;
    bcopy((char *)newname, (char *)name, ((int)*length) * sizeof(oid));
    *write_method = 0;
    *var_len = sizeof(long);

    switch (vp->magic) {

      case ipMRouteUpstreamNeighbor: 
         return (u_char *) &gt->gt_route->rt_gateway;

      case ipMRouteInIfIndex:
         long_return = gt->gt_route->rt_parent;
         return (u_char *) &long_return;

      case ipMRouteUpTime: {
         time_t currtime;
         time(&currtime);
         long_return = (currtime - gt->gt_ctime)*100;
         return (u_char *) &long_return;
      }

      case ipMRouteExpiryTime:
         long_return = 5*((gt->gt_timer+4)/5); /* round up to nearest 5 */
         long_return = (long_return + secs_remaining_offset()) * 100;
         return (u_char *) &long_return;

      case ipMRoutePkts:
         refresh_sg(&sg_req, gt, st);
         long_return = sg_req.pktcnt;
         return (u_char *) &long_return;

      case ipMRouteOctets:
         refresh_sg(&sg_req, gt, st);
         long_return = sg_req.bytecnt;
         return (u_char *) &long_return;

      case ipMRouteDifferentInIfIndexes:
         refresh_sg(&sg_req, gt, st);
         long_return = sg_req.wrong_if;
         return (u_char *) &long_return;

      case ipMRouteProtocol:
         long_return = 4;
         return (u_char *) &long_return;

    default:
       ERROR("");
    }
    return NULL;
}

/*
 * Implements the IP Multicast Routing Next Hop Table portion of the Multicast
 * MIB
 */
u_char *
o_ipMRouteNextHopTable(vp, name, length, exact, var_len, write_method)
    register struct variable *vp;   /* IN - pointer to variable entry that points here */
    register oid	*name;	    /* IN/OUT - input name requested, output name found */
    register int	*length;    /* IN/OUT - length of input and output oid's */
    int			exact;	    /* IN - TRUE if an exact match was requested. */
    int			*var_len;   /* OUT - length of variable or 0 if function returned. */
    int			(**write_method)(); /* OUT - pointer to function to set variable, otherwise 0 */
{
    u_long src, grp, mask, addr;
    vifi_t   vifi;
    struct gtable *gt;
    struct stable *st;
    oid        newname[MAX_NAME_LEN];
    int        len;

    /* Copy name OID to new OID */
    bcopy((char *)vp->name, (char *)newname, (int)vp->namelen * sizeof(oid));

    if (exact) {
	    if (*length != vp->namelen + 17)
		return NULL;

      if (!get_address(name, *length, &grp, vp->namelen)
       || !get_address(name, *length, &src, vp->namelen+4)
       || !get_address(name, *length, &mask, vp->namelen+8)
       || !get_address(name, *length, &addr, vp->namelen+13)
       || grp!=addr
       || mask!=0xFFFFFFFF
       || (!(gt=find_grp(grp)))
       || (!(st=find_grp_src(gt,src))))
		return NULL;

      vifi = name[vp->namelen+12];
      if (!(VIFM_ISSET(vifi, gt->gt_route->rt_children)))
      return NULL;

       bcopy((char *)name, (char *)newname, ((int)*length) * sizeof(oid));
	 } else {
       len = *length;
       if (compare(name, *length, vp->name, vp->namelen) < 0)
          len = vp->namelen;

	    if (len < vp->namelen + 17) { /* get first entry */

         get_address(name, len, &grp, vp->namelen);
         get_address(name, len, &src, vp->namelen+4);
         get_address(name, len, &mask, vp->namelen+8);

         /* Find first child vif */
         vifi=0;
         if (!next_child(&gt, &st, grp, src, mask, &vifi))
            return NULL;

         put_address(newname, gt->gt_mcastgrp, vp->namelen);
         put_address(newname, st->st_origin,   vp->namelen+4);
         put_address(newname, 0xFFFFFFFF,      vp->namelen+8);
   		newname[vp->namelen+12] = vifi;
         put_address(newname, gt->gt_mcastgrp, vp->namelen+13);

	    } else {  /* get next entry given previous */
		   vifi = name[vp->namelen+12]+1;
         if (!get_address(name, *length, &grp,  vp->namelen)
          || !get_address(name, *length, &src,  vp->namelen+4)
          || !get_address(name, *length, &mask, vp->namelen+8)
          || !next_child(&gt, &st, grp, src, mask, &vifi))
            return NULL;

         put_address(newname, gt->gt_mcastgrp, vp->namelen);
         put_address(newname, st->st_origin,   vp->namelen+4);
         put_address(newname, 0xFFFFFFFF,      vp->namelen+8);
		   newname[vp->namelen+12] = vifi;
         put_address(newname, gt->gt_mcastgrp, vp->namelen+13);
	    }
    }

    /* Save new OID */
    *length = vp->namelen + 17;
    bcopy((char *)newname, (char *)name, ((int)*length) * sizeof(oid));
    *write_method = 0;
    *var_len = sizeof(long);

    switch (vp->magic) {

      case ipMRouteNextHopState:
         long_return = (VIFM_ISSET(vifi, gt->gt_grpmems))? 2 : 1;
         return (u_char *) &long_return;

      /* Currently equal to ipMRouteUpTime */
      case ipMRouteNextHopUpTime: {
         time_t currtime;
         time(&currtime);
         long_return = (currtime - gt->gt_ctime)*100;
         return (u_char *) &long_return;
      }

      case ipMRouteNextHopExpiryTime:
         long_return = 5*((gt->gt_prsent_timer+4)/5); /* round up to nearest 5*/
         long_return = (long_return + secs_remaining_offset()) * 100;
         return (u_char *) &long_return;

      case ipMRouteNextHopClosestMemberHops:
         long_return = 0;
         return (u_char *) &long_return;

      case ipMRouteNextHopProtocol:
         long_return = 4;
         return (u_char *) &long_return;

    default:
       ERROR("");
    }
    return NULL;
}

/* sync_timer is called by timer() every TIMER_INTERVAL seconds.
 * Its job is to record this time so that we can compute on demand
 * the approx # seconds remaining until the next timer() call
 */
static time_t lasttimer;

void
sync_timer()
{
    time(&lasttimer);
}

int /* in range [-TIMER_INTERVAL..0] */
secs_remaining_offset()
{
   time_t tm;

   time(&tm);
   return lasttimer-tm;
}
